Micron Document
C.S.Burner 🪙 ━►🔥GIT Node

Node / reticulum_mirror / MeshChatX / files / meshchatx / src / frontend / components / messages / PaperMessageModal.vue

Displaying Raw • Download

meshchatx/src/frontend/components/messages/PaperMessageModal.vue 37d4b317b90c0e7ffb8f9ac47e58263f0c53db7d (37d4b317) Text, 17.30 KB

Tff7b72<templateTff7b72>
Tff7b72<div
Te6edf3class=Ta5d6ff"Ta5d6fffixed inset-0 z-[150] flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm transition-opacityTa5d6ff"
@click.Te6edf3self=Ta5d6ff"Ta5d6ffcloseTa5d6ff"
Tff7b72>
Tff7b72<div
Te6edf3class=Ta5d6ff"Ta5d6ffw-full max-w-lg bg-white dark:bg-zinc-900 rounded-3xl shadow-2xl overflow-hidden transform transition-all scale-100Ta5d6ff"
Tff7b72>
T8b949e<!-- header -->
Tff7b72<div
Te6edf3class=Ta5d6ff"Ta5d6ffpx-6 py-5 border-b border-gray-100 dark:border-zinc-800 flex items-center justify-between bg-gray-50/50 dark:bg-zinc-900/50Ta5d6ff"
Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex items-center gap-3Ta5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffp-2 bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400 rounded-xlTa5d6ff"Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffqrcodeTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffsize-6Ta5d6ff" Tff7b72/>
Tff7b72</div>
Tff7b72<h3 Te6edf3class=Ta5d6ff"Ta5d6fftext-xl font-bold text-gray-900 dark:text-white tracking-tightTa5d6ff"Tff7b72>Paper MessageTff7b72</h3>
Tff7b72</div>
Tff7b72<button
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffp-2 text-gray-400 hover:text-gray-600 dark:hover:text-zinc-200 hover:bg-gray-100 dark:hover:bg-zinc-800 rounded-full transition-allTa5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffcloseTa5d6ff"
Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffcloseTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffsize-6Ta5d6ff" Tff7b72/>
Tff7b72</button>
Tff7b72</div>

Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffp-4 sm:p-6 flex flex-col items-centerTa5d6ff"Tff7b72>
Tff7b72<div Te6edf3v-if=Ta5d6ff"Ta5d6ffisLoadingTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffflex flex-col items-center py-8Ta5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffsize-12 border-4 border-blue-500/20 border-t-blue-500 rounded-full animate-spinTa5d6ff"Tff7b72>Tff7b72</div>
Tff7b72<p Te6edf3class=Ta5d6ff"Ta5d6ffmt-4 text-xs text-gray-500 dark:text-gray-400 font-mediumTa5d6ff"Tff7b72>Generating Paper Message...Tff7b72</p>
Tff7b72</div>
Tff7b72<template Te6edf3v-else-if=Ta5d6ff"Ta5d6ffuriTa5d6ff"Tff7b72>
T8b949e<!-- QR code container -->
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffp-3 bg-white rounded-2xl shadow-inner border border-gray-100 mb-6 relative groupTa5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffsize-40 sm:size-48 flex items-center justify-center overflow-hiddenTa5d6ff"Tff7b72>
Tff7b72<canvas Te6edf3ref=Ta5d6ff"Ta5d6ffqrcodeTa5d6ff"Tff7b72>Tff7b72</canvas>
Tff7b72</div>
Tff7b72<div
Te6edf3class=Ta5d6ff"Ta5d6ffabsolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity bg-white/10 backdrop-blur-[2px] rounded-2xl pointer-events-noneTa5d6ff"
Tff7b72>
Tff7b72<div
Te6edf3class=Ta5d6ff"Ta5d6ffp-2 bg-white/90 dark:bg-zinc-900/90 rounded-xl shadow-xl border border-gray-200 dark:border-zinc-700Ta5d6ff"
Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffmagnify-plus-outlineTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffsize-6 text-blue-500Ta5d6ff" Tff7b72/>
Tff7b72</div>
Tff7b72</div>
Tff7b72</div>

Tff7b72<div Te6edf3v-if=Ta5d6ff"Ta5d6ffrecipientHashTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffw-full space-y-3Ta5d6ff"Tff7b72>
Tff7b72<div
Te6edf3class=Ta5d6ff"Ta5d6ffbg-gray-50 dark:bg-zinc-800/50 rounded-2xl p-3 border border-gray-100 dark:border-zinc-700/50Ta5d6ff"
Tff7b72>
Tff7b72<label
Te6edf3class=Ta5d6ff"Ta5d6ffblock text-[9px] font-bold text-gray-400 dark:text-zinc-500 uppercase tracking-widest mb-1.5Ta5d6ff"
Tff7b72>
LXMF URI
Tff7b72</label>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex gap-2Ta5d6ff"Tff7b72>
Tff7b72<div
Te6edf3class=Ta5d6ff"Ta5d6ffflex-1 font-mono text-[10px] break-all text-gray-600 dark:text-zinc-300 bg-white dark:bg-zinc-900 p-2 rounded-lg border border-gray-200 dark:border-zinc-700 max-h-20 overflow-y-autoTa5d6ff"
Tff7b72>
{{ uri }}
Tff7b72</div>
Tff7b72<button
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffsize-9 flex items-center justify-center bg-white dark:bg-zinc-900 text-gray-500 dark:text-zinc-400 rounded-lg border border-gray-200 dark:border-zinc-700 hover:bg-blue-50 hover:text-blue-600 hover:border-blue-200 transition-all shadow-smTa5d6ff"
Te6edf3title=Ta5d6ff"Ta5d6ffCopy URITa5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffcopyUriTa5d6ff"
Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffcontent-copyTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffsize-4Ta5d6ff" Tff7b72/>
Tff7b72</button>
Tff7b72</div>
Tff7b72</div>

Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex gap-2 pt-1Ta5d6ff"Tff7b72>
Tff7b72<button
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffflex-1 flex items-center justify-center gap-2 py-2.5 px-4 bg-blue-600 hover:bg-blue-700 text-white rounded-xl font-bold shadow-lg shadow-blue-500/20 transition-all active:scale-[0.98] text-smTa5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffprintQRCodeTa5d6ff"
Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffprinterTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffsize-4Ta5d6ff" Tff7b72/>
Print
Tff7b72</button>
Tff7b72<button
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffflex-1 flex items-center justify-center gap-2 py-2.5 px-4 bg-emerald-600 hover:bg-emerald-700 text-white rounded-xl font-bold shadow-lg shadow-emerald-500/20 transition-all active:scale-[0.98] text-smTa5d6ff"
Te6edf3:disabled=Ta5d6ff"Ta5d6ffisSendingTa5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffsendPaperMessageTa5d6ff"
Tff7b72>
Tff7b72<template Te6edf3v-if=Ta5d6ff"Ta5d6ffisSendingTa5d6ff"Tff7b72>
Tff7b72<div
Te6edf3class=Ta5d6ff"Ta5d6ffsize-4 border-2 border-white/20 border-t-white rounded-full animate-spinTa5d6ff"
Tff7b72>Tff7b72</div>
Sending...
Tff7b72</template>
Tff7b72<template v-elseTff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffsendTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffsize-4Ta5d6ff" Tff7b72/>
Send
Tff7b72</template>
Tff7b72</button>
Tff7b72</div>
Tff7b72</div>
Tff7b72<div v-else Te6edf3class=Ta5d6ff"Ta5d6ffw-full space-y-3Ta5d6ff"Tff7b72>
Tff7b72<div
Te6edf3class=Ta5d6ff"Ta5d6ffbg-gray-50 dark:bg-zinc-800/50 rounded-2xl p-3 border border-gray-100 dark:border-zinc-700/50Ta5d6ff"
Tff7b72>
Tff7b72<label
Te6edf3class=Ta5d6ff"Ta5d6ffblock text-[9px] font-bold text-gray-400 dark:text-zinc-500 uppercase tracking-widest mb-1.5Ta5d6ff"
Tff7b72>
LXMF URI
Tff7b72</label>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex gap-2Ta5d6ff"Tff7b72>
Tff7b72<div
Te6edf3class=Ta5d6ff"Ta5d6ffflex-1 font-mono text-[10px] break-all text-gray-600 dark:text-zinc-300 bg-white dark:bg-zinc-900 p-2 rounded-lg border border-gray-200 dark:border-zinc-700 max-h-20 overflow-y-autoTa5d6ff"
Tff7b72>
{{ uri }}
Tff7b72</div>
Tff7b72<button
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffsize-9 flex items-center justify-center bg-white dark:bg-zinc-900 text-gray-500 dark:text-zinc-400 rounded-lg border border-gray-200 dark:border-zinc-700 hover:bg-blue-50 hover:text-blue-600 hover:border-blue-200 transition-all shadow-smTa5d6ff"
Te6edf3title=Ta5d6ff"Ta5d6ffCopy URITa5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffcopyUriTa5d6ff"
Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffcontent-copyTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffsize-4Ta5d6ff" Tff7b72/>
Tff7b72</button>
Tff7b72</div>
Tff7b72</div>

Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex gap-2 pt-1Ta5d6ff"Tff7b72>
Tff7b72<button
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffflex-1 flex items-center justify-center gap-2 py-2.5 px-4 bg-blue-600 hover:bg-blue-700 text-white rounded-xl font-bold shadow-lg shadow-blue-500/20 transition-all active:scale-[0.98] text-smTa5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffprintQRCodeTa5d6ff"
Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffprinterTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffsize-4Ta5d6ff" Tff7b72/>
Print
Tff7b72</button>
Tff7b72<button
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffflex-1 flex items-center justify-center gap-2 py-2.5 px-4 bg-gray-100 dark:bg-zinc-800 hover:bg-gray-200 dark:hover:bg-zinc-700 text-gray-700 dark:text-zinc-200 rounded-xl font-bold transition-all active:scale-[0.98] text-smTa5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffdownloadQRCodeTa5d6ff"
Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffdownloadTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffsize-4Ta5d6ff" Tff7b72/>
Save
Tff7b72</button>
Tff7b72</div>
Tff7b72</div>
Tff7b72</template>
Tff7b72<div v-else Te6edf3class=Ta5d6ff"Ta5d6ffflex flex-col items-center py-12 text-centerTa5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffp-4 bg-red-50 dark:bg-red-900/20 text-red-500 rounded-full mb-4Ta5d6ff"Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffalert-circle-outlineTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffsize-12Ta5d6ff" Tff7b72/>
Tff7b72</div>
Tff7b72<h4 Te6edf3class=Ta5d6ff"Ta5d6fftext-lg font-bold text-gray-900 dark:text-white mb-2Ta5d6ff"Tff7b72>Message Not AvailableTff7b72</h4>
Tff7b72<p Te6edf3class=Ta5d6ff"Ta5d6fftext-sm text-gray-500 dark:text-gray-400 max-w-xsTa5d6ff"Tff7b72>
The original message bytes are no longer available in the router queue to generate a signed
paper message.
Tff7b72</p>
Tff7b72<button
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffmt-6 py-2.5 px-6 bg-gray-100 dark:bg-zinc-800 text-gray-700 dark:text-zinc-200 rounded-xl font-bold hover:bg-gray-200 dark:hover:bg-zinc-700 transition-allTa5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffcloseTa5d6ff"
Tff7b72>
Close
Tff7b72</button>
Tff7b72</div>
Tff7b72</div>
Tff7b72</div>
Tff7b72</div>
Tff7b72</template>

Tff7b72<scriptTff7b72>
import QRCode from "qrcode";
import MaterialDesignIcon from "../MaterialDesignIcon.vue";
import ToastUtils from "../../js/ToastUtils";

export default {
name: "PaperMessageModal",
components: { MaterialDesignIcon },
props: {
messageHash: {
type: String,
required: false,
default: null,
},
initialUri: {
type: String,
required: false,
default: null,
},
recipientHash: {
type: String,
required: false,
default: null,
},
},
emits: ["close"],
data() {
return {
uri: this.initialUri,
isLoading: !this.initialUri,
isSending: false,
};
},
async mounted() {
if (!this.uri && this.messageHash) {
await this.fetchUri();
} else if (this.uri) {
this.$nextTick(() => {
this.renderQRCode();
});
}
},
methods: {
async fetchUri() {
try {
this.isLoading = true;
const response = await window.axios.get(`/api/v1/lxmf-messages/${this.messageHash}/uri`);
this.uri = response.data.uri;
if (this.uri) {
this.$nextTick(() => {
this.renderQRCode();
});
}
} catch (e) {
console.error("Failed to fetch message URI:", e);
} finally {
this.isLoading = false;
}
},
async renderQRCode() {
if (!this.uri || !this.$refs.qrcode) return;

try {
await QRCode.toCanvas(this.$refs.qrcode, this.uri, {
width: 256,
margin: 2,
color: {
dark: "#000000",
light: "#ffffff",
},
errorCorrectionLevel: "L",
});

const el = this.$refs.qrcode;
if (el) {
el.style.maxWidth = "100%";
el.style.height = "auto";
el.classList.add("rounded-lg");
}
} catch (err) {
console.error("Failed to render QR code:", err);
ToastUtils.error(this.$t("messages.failed_render_qr"));
}
},
close() {
this.$emit("close");
},
async copyUri() {
try {
await navigator.clipboard.writeText(this.uri);
ToastUtils.success(this.$t("messages.uri_copied"));
} catch {
ToastUtils.error(this.$t("messages.failed_copy_uri"));
}
},
downloadQRCode() {
const canvas = this.$refs.qrcode;
if (!canvas) return;

const dataUrl = canvas.toDataURL("image/png");
if (dataUrl) {
const link = document.createElement("a");
link.download = `lxmf-paper-message-${this.messageHash ? this.messageHash.substring(0, 8) : Date.now()}.png`;
link.href = dataUrl;
link.click();
}
},
async sendPaperMessage() {
if (!this.recipientHash || !this.uri) return;

try {
this.isSending = true;

// build lxmf message
const lxmf_message = {
destination_hash: this.recipientHash,
content: `Please scan the attached Paper Message or manually ingest this URI: ${this.uri}`,
fields: {},
};

// get data url from canvas if available
const canvas = this.$refs.qrcode;
if (canvas) {
const dataUrl = canvas.toDataURL("image/png");
const imageBytes = dataUrl.split(",")[1];
lxmf_message.fields.image = {
image_type: "png",
image_bytes: imageBytes,
name: "paper_message_qr.png",
};
}

// send message
const response = await window.axios.post(`/api/v1/lxmf-messages/send`, {
delivery_method: "opportunistic",
lxmf_message: lxmf_message,
});

if (response.data.lxmf_message) {
ToastUtils.success(this.$t("messages.paper_message_sent"));
this.close();
} else {
ToastUtils.error(response.data.message || "Failed to send paper message");
}
} catch (err) {
console.error("Failed to send paper message:", err);
ToastUtils.error(this.$t("messages.failed_send_paper"));
} finally {
this.isSending = false;
}
},
printQRCode() {
const canvas = this.$refs.qrcode;
if (!canvas) return;

const dataUrl = canvas.toDataURL("image/png");
if (!dataUrl) return;

const printWindow = window.open("", "_blank");
if (!printWindow) {
ToastUtils.error("Pop-up blocked. Please allow pop-ups to print.");
return;
}

printWindow.document.write(`
Tff7b72<htmlTff7b72>
Tff7b72<headTff7b72>
Tff7b72<titleTff7b72>LXMF Paper MessageTff7b72</title>
Tff7b72<styleTff7b72>
body { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; font-family: sans-serif; }
img { width: 400px; height: 400px; margin-bottom: 20px; }
.hash { font-family: monospace; font-size: 12px; color: #666; }
@media print { body { height: auto; padding: 20px; } }
Tff7b72</style>
Tff7b72</head>
Tff7b72<bodyTff7b72>
Tff7b72<h1Tff7b72>LXMF Paper MessageTff7b72</h1>
Tff7b72<img Te6edf3src=Ta5d6ff"${dataUrl}" Tff7b72/>
Tff7b72<div Te6edf3class=Ta5d6ff"hash"Tff7b72>Message Hash: ${this.messageHash}Tff7b72</div>
Tff7b72<pTff7b72>Scan this code with an LXMF-compatible app to read the message.Tff7b72</p>
Tff7b72<scriptTff7b72>window.onload = () => { window.print(); window.close(); }</scr' + 'ipt>
Tff7b72</body>
Tff7b72</html>
`);
printWindow.document.close();
},
},
};
Tff7b72</script>

Tff7b72<style scopedTff7b72>
.audio-controls-light::-webkit-media-controls-panel {
background-color: rgba(255, 255, 255, 0.2);
}
.audio-controls-dark::-webkit-media-controls-panel {
background-color: rgba(0, 0, 0, 0.2);
}
Tff7b72</style>

Served by rngit 1.4.2 - Generated in 0.05s